#include <vmm.h>
#include <sync.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <error.h>
#include <pmm.h>
#include <x86.h>
#include <swap.h>
#include <kmalloc.h>

/* 
  vmm design include two parts: mm_struct (mm) & vma_struct (vma)
  mm is the memory manager for the set of continuous virtual memory  
  area which have the same PDT. vma is a continuous virtual memory area.
  There a linear link list for vma & a redblack link list for vma in mm.
  虚拟内存管理设计包括两个部分：mm_struct（mm）和 vma_struct（vma）。
  mm 是一组具有相同页目录表（PDT）的连续虚拟内存区域的内存管理器。
  vma 是一个连续的虚拟内存区域。在 mm 中有一个用于 vma 的线性链表和一个用于 vma 的红黑树链表。
---------------
  mm related functions: 与 mm 相关的函数：
   golbal functions 全局函数
     struct mm_struct * mm_create(void)
     void mm_destroy(struct mm_struct *mm)
     int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)
--------------
  vma related functions: 与 vma 相关的函数：
   global functions 全局函数
     struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...)
     void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma)
     struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr)
   local functions 本地函数
     inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next)
---------------
   check correctness functions 确认正确性函数 
     void check_vmm(void);
     void check_vma_struct(void);
     void check_pgfault(void);
*/

static void check_vmm(void);
static void check_vma_struct(void);
static void check_pgfault(void);

// mm_create -  alloc a mm_struct & initialize it.
/**
 * 创建并初始化一个内存管理结构体
 * 
 * 此函数负责分配并初始化一个`mm_struct`结构体，该结构体用于描述一个进程的内存空间状态
 * 它包括内存映射列表、页目录、映射缓存等重要信息
 * 
 * @return 分配并初始化后的`mm_struct`结构体指针，如果分配失败则返回NULL
 */
struct mm_struct *
mm_create(void) {
    // 分配一个mm_struct结构体的空间
    struct mm_struct *mm = kmalloc(sizeof(struct mm_struct));
    // 检查是否成功分配了内存
    if (mm != NULL) {
        // 初始化内存映射列表
        list_init(&(mm->mmap_list));
        // 设置映射缓存为NULL，表示尚未缓存任何映射
        mm->mmap_cache = NULL;
        // 设置页目录为NULL，表示尚未分配页目录
        mm->pgdir = NULL;
        // 初始化映射计数为0，表示尚未创建任何内存映射
        mm->map_count = 0;
        // 如果交换空间初始化成功，则为当前内存管理结构体进行交换空间初始化
        if (swap_init_ok) swap_init_mm(mm);
        else mm->sm_priv = NULL;
    }
    // 返回分配并初始化后的内存管理结构体指针
    return mm;
}

// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end)
/**
 * 创建并初始化一个虚拟内存区域（VMA）结构体。
 * 
 * @param vm_start 虚拟内存区域的起始地址。
 * @param vm_end 虚拟内存区域的结束地址。
 * @param vm_flags 虚拟内存区域的标志，表示内存区域的权限和特性。
 * 
 * @return 返回指向新创建的vma_struct结构体的指针，如果内存分配失败，则返回NULL。
 */
struct vma_struct *
vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) {
    // 分配vma_struct结构体所需的内存空间
    struct vma_struct *vma = kmalloc(sizeof(struct vma_struct));
    // 检查内存是否成功分配
    if (vma != NULL) {
        // 初始化vma_struct的成员变量
        vma->vm_start = vm_start;
        vma->vm_end = vm_end;
        vma->vm_flags = vm_flags;
    }
    // 返回指向新创建的vma_struct结构体的指针，或在内存分配失败时返回NULL
    return vma;
}


// find_vma - find a vma  (vma->vm_start <= addr <= vma_vm_end)
/**
 * 查找给定地址对应的虚拟内存区域(VMA)
 * 
 * @param mm 进程的内存描述符，包含所有VMA的列表
 * @param addr 要查找的地址
 * @return 返回包含给定地址的VMA指针，如果未找到则返回NULL
 * 
 * 此函数首先检查mmap_cache是否包含所需的VMA，以加速查找过程
 * 如果mmap_cache未命中，则遍历VMA列表，直到找到包含给定地址的VMA或确定不存在这样的VMA
 * 如果找到了合适的VMA，它将更新mmap_cache以供后续查找使用
 */
struct vma_struct *
find_vma(struct mm_struct *mm, uintptr_t addr) {
    struct vma_struct *vma = NULL;// 初始化VMA指针为NULL
    if (mm != NULL) {// 检查传入的内存描述符是否有效
        // 检查mmap_cache是否包含所需的VMA
        vma = mm->mmap_cache;
        if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) {
                // 如果mmap_cache未命中，则开始遍历VMA列表
                bool found = 0;// 初始化找到标志为0
                // 获取VMA列表的头指针
                list_entry_t *list = &(mm->mmap_list), *le = list;
                while ((le = list_next(le)) != list) { // 遍历VMA列表
                    vma = le2vma(le, list_link);// 将链表项转换为VMA结构
                    // 检查当前VMA是否包含给定地址
                    if (vma->vm_start<=addr && addr < vma->vm_end) {
                        found = 1;// 找到合适的VMA
                        break;// 结束循环
                    }
                }
                if (!found) {// 如果未找到合适的VMA
                    vma = NULL;// 将VMA指针设置为NULL
                }
        }
        // 如果找到了合适的VMA，更新mmap_cache
        if (vma != NULL) {
            mm->mmap_cache = vma;// 更新mmap_cache以加速后续查找
        }
    }
    return vma;
}


// check_vma_overlap - check if vma1 overlaps vma2 ?
/**
 * 检查两个虚拟内存区域（VMA）是否有重叠
 * 
 * 此函数的目的是确保给定的两个虚拟内存区域（VMA）在内存中是正确排序且没有重叠的
 * 它通过断言来检查以下条件：
 * 1. 前一个VMA的起始地址小于其结束地址，确保前一个VMA的地址范围是有效的
 * 2. 前一个VMA的结束地址小于等于下一个VMA的起始地址，确保两个VMA之间没有重叠
 * 3. 后一个VMA的起始地址小于其结束地址，确保后一个VMA的地址范围是有效的
 * 
 * @param prev 指向前一个虚拟内存区域（VMA）的结构体指针
 * @param next 指向后一个虚拟内存区域（VMA）的结构体指针
 */
static inline void
check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) {
    assert(prev->vm_start < prev->vm_end);// 确保前一个VMA的地址范围是有效的
    assert(prev->vm_end <= next->vm_start);// 确保两个VMA之间没有重叠
    assert(next->vm_start < next->vm_end);// 确保后一个VMA的地址范围是有效的
}


// insert_vma_struct -insert vma in mm's list link
/**
 * 将VMA（虚拟内存区域）结构插入到内存描述符的链表中。
 *
 * 此函数负责将新的VMA结构插入到进程的VMA链表中的正确位置，确保VMA结构按起始地址升序排列。
 * 它还检查与相邻VMA结构的重叠情况，以确保内存段管理的一致性。
 *
 * @param mm 指向内存描述符结构 `struct mm_struct` 的指针，表示一个进程的内存空间。
 * @param vma 指向要插入的VMA结构 `struct vma_struct` 的指针，描述一个内存区域。
 */
void
insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) {
    // 断言VMA结构的起始地址小于结束地址，确保VMA结构的有效性。
    assert(vma->vm_start < vma->vm_end);
    // 指向内存描述符中的VMA链表。
    list_entry_t *list = &(mm->mmap_list);
     // 遍历链表以找到新VMA结构的正确插入位置。
    list_entry_t *le_prev = list, *le_next;

        list_entry_t *le = list;
        // 遍历链表以找到新VMA结构的正确插入位置
        while ((le = list_next(le)) != list) {
            struct vma_struct *mmap_prev = le2vma(le, list_link);
            // 如果当前VMA的起始地址大于新VMA的起始地址，则跳出循环
            if (mmap_prev->vm_start > vma->vm_start) {
                break;
            }
            le_prev = le;
        }
    // 获取下一个链表项
    le_next = list_next(le_prev);

    /* check overlap */
    // 检查前一个VMA结构是否与新VMA结构重叠
    if (le_prev != list) {
        check_vma_overlap(le2vma(le_prev, list_link), vma);
    }
    // 检查下一个VMA结构是否与新VMA结构重叠
    if (le_next != list) {
        check_vma_overlap(vma, le2vma(le_next, list_link));
    }
    // 设置VMA结构所属的内存描述符
    vma->vm_mm = mm;
    // 将新VMA结构插入链表
    list_add_after(le_prev, &(vma->list_link));
    // 增加内存描述符中的映射计数
    mm->map_count ++;
}

// mm_destroy - free mm and mm internal fields
/**
 * mm_destroy - 销毁一个内存管理结构（mm_struct）及其关联的虚拟内存区域（VMA）
 * @mm: 指向要销毁的内存管理结构的指针
 * 
 * 此函数遍历并销毁与内存管理结构（mm_struct）关联的所有虚拟内存区域（VMA），
 * 然后释放内存管理结构本身所占用的内存。这样做是为了确保在销毁内存管理结构之前，
 * 所有相关的资源都被正确地释放。
 */
void
mm_destroy(struct mm_struct *mm) {
    // 获取内存映射列表的头指针
    list_entry_t *list = &(mm->mmap_list), *le;
    // 遍历内存映射列表，直到回到起点
    while ((le = list_next(list)) != list) {
        // 从列表中删除当前虚拟内存区域的项
        list_del(le);
        // 释放虚拟内存区域结构的内存
        kfree(le2vma(le, list_link));
        //kfree(le2vma(le, list_link), sizeof(struct vma_struct));  //kfree vma        
    }
    // 释放内存管理结构本身的内存
    kfree(mm); //kfree mm
    //kfree(mm, sizeof(struct mm_struct)); //kfree mm
    // 将指针设置为NULL，表示该结构已被销毁
    mm=NULL;
}

// vmm_init - initialize virtual memory management
//          - now just call check_vmm to check correctness of vmm
/**
 * 初始化虚拟内存管理(VMM)系统。
 * 此函数通过执行一系列检查来确保VMM系统可以正确初始化和运行。
 */
void
vmm_init(void) {
    // 检查VMM系统的状态和环境，以确保其能够正常工作。
    check_vmm();
}

// check_vmm - check correctness of vmm
/**
 * 检查虚拟内存管理（VMM）的完整性
 * 
 * 此函数的目的是确保虚拟内存管理系统的正确性通过检查内存区域结构（VMA）、页面故障处理以及免费页面计数的 consistency 来实现
 * 它首先保存当前的免费页面数量，然后执行与 VMA 和页面故障相关的检查，最后确认免费页面数量未发生变化
 * 这是为了确保在检查过程中，内存状态没有因为错误或意外的修改而改变，从而验证内存管理的正确性
 */
static void
check_vmm(void) {
    // 保存当前的免费页面数量，用于后续的 consistency 检查
    size_t nr_free_pages_store = nr_free_pages();
    // 检查虚拟内存区域（VMA）结构的正确性
    check_vma_struct();
    // 检查页面故障处理的正确性
    check_pgfault();
    // 确保在检查过程中免费页面数量未发生变化，表明内存管理操作是正确的
    // assert(nr_free_pages_store == nr_free_pages());
    // 如果所有检查都通过，输出成功信息
    cprintf("check_vmm() succeeded.\n");
}

//测试虚拟内存区域（VMA）结构的创建、插入和查找功能。
static void
check_vma_struct(void) {
    // 记录当前空闲页面数量
    size_t nr_free_pages_store = nr_free_pages();

    struct mm_struct *mm = mm_create();// 创建内存管理结构 mm
    assert(mm != NULL);// 确保 mm 不为 NULL

    int step1 = 10, step2 = step1 * 10;// 定义两个步骤的步数

    int i;
    for (i = step1; i >= 1; i --) {// 第一步：创建并插入10个VMA
        // 创建 VMA 结构
        struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0);
        assert(vma != NULL);// 确保 VMA 不为 NULL
        insert_vma_struct(mm, vma); //将 VMA 插入到 mm 中
    }

    for (i = step1 + 1; i <= step2; i ++) {// 第二步：创建并插入90个VMA
        // 创建 VMA 结构
        struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0);
        assert(vma != NULL);// 确保 VMA 不为 NULL
        insert_vma_struct(mm, vma);// 将 VMA 插入到 mm 中
    }
    // 获取 VMA 链表的第一个节点
    list_entry_t *le = list_next(&(mm->mmap_list));

    for (i = 1; i <= step2; i ++) {// 验证插入顺序
        assert(le != &(mm->mmap_list));// 确保节点不为空
        struct vma_struct *mmap = le2vma(le, list_link);// 将链表节点转换为 VMA 结构
        // 确认 VMA 的起始和结束地址
        assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2);
        le = list_next(le);// 移动到下一个节点
    }

    for (i = 5; i <= 5 * step2; i +=5) {// 查找特定地址范围内的 VMA
        struct vma_struct *vma1 = find_vma(mm, i);// 查找地址 i 处的 VMA
        assert(vma1 != NULL);// 确保找到 VMA
        // 查找地址 i + 1 处的 VMA
        struct vma_struct *vma2 = find_vma(mm, i+1);
        assert(vma2 != NULL);// 确保找到 VMA
        // 查找地址 i + 2 处的 VMA
        struct vma_struct *vma3 = find_vma(mm, i+2);
        assert(vma3 == NULL);// 确保未找到 VMA
        // 查找地址 i + 3 处的 VMA
        struct vma_struct *vma4 = find_vma(mm, i+3);
        assert(vma4 == NULL);// 确保未找到 VMA
        // 查找地址 i + 4 处的 VMA
        struct vma_struct *vma5 = find_vma(mm, i+4);
        assert(vma5 == NULL);// 确保未找到 VMA
        // 确认 VMA1 的起始和结束地址
        assert(vma1->vm_start == i  && vma1->vm_end == i  + 2);
        // 确认 VMA2 的起始和结束地址
        assert(vma2->vm_start == i  && vma2->vm_end == i  + 2);
    }
    // 检查小于5的地址范围内是否存在 VMA
    for (i =4; i>=0; i--) {
        // 查找地址 i 处的 VMA
        struct vma_struct *vma_below_5= find_vma(mm,i);
        if (vma_below_5 != NULL ) {// 如果找到 VMA
           cprintf("vma_below_5: i %x, start %x, end %x\n",i, vma_below_5->vm_start, vma_below_5->vm_end); 
        }
        assert(vma_below_5 == NULL);// 确保未找到 VMA
    }

    mm_destroy(mm);// 销毁 mm 结构

    // 确保释放的页面数量与初始记录一致
    // assert(nr_free_pages_store == nr_free_pages());
    // 输出成功信息
    cprintf("check_vma_struct() succeeded!\n");
}

struct mm_struct *check_mm_struct;

// check_pgfault - check correctness of pgfault handler
// 检查页故障处理的正确性
static void
check_pgfault(void) {
    // 保存当前空闲页面的数量，用于后续检查
    size_t nr_free_pages_store = nr_free_pages();
    // 创建内存管理结构体
    check_mm_struct = mm_create();
    // 确保内存管理结构体创建成功
    assert(check_mm_struct != NULL);
    // 将新创建的内存管理结构体赋值给局部变量mm
    struct mm_struct *mm = check_mm_struct;
    // 将引导程序的页目录复制到新创建的内存管理结构体中
    pde_t *pgdir = mm->pgdir = boot_pgdir;
    // 确保页目录的第0项是空的
    assert(pgdir[0] == 0);
    // 创建一个虚拟内存区域结构体，具有写权限
    struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE);
    // 确保虚拟内存区域结构体创建成功
    assert(vma != NULL);
    // 将虚拟内存区域结构体插入到内存管理结构体中
    insert_vma_struct(mm, vma);
    // 定义一个地址，用于访问虚拟内存
    uintptr_t addr = 0x100;
    // 确保通过该地址可以找到之前插入的虚拟内存区域
    assert(find_vma(mm, addr) == vma);
    // 初始化一个累加器，用于校验写入的数据
    int i, sum = 0;
    // 写入数据到虚拟内存，并累加
    for (i = 0; i < 100; i ++) {
        *(char *)(addr + i) = i;
        sum += i;
    }
    // 读取虚拟内存中的数据，并减去，最终结果应为0
    for (i = 0; i < 100; i ++) {
        sum -= *(char *)(addr + i);
    }
    // 确保累加器的值为0，证明数据读写正确
    assert(sum == 0);
    // 移除页目录中的相应页面
    page_remove(pgdir, ROUNDDOWN(addr, PGSIZE));
    // 释放第0项页目录对应的页面
    free_page(pde2page(pgdir[0]));
    // 将页目录的第0项设置为空
    pgdir[0] = 0;
    // 将内存管理结构体中的页目录设置为空
    mm->pgdir = NULL;
    // 销毁内存管理结构体
    mm_destroy(mm);
    // 将检查用的内存管理结构体设置为空
    check_mm_struct = NULL;
    // 确保空闲页面的数量没有变化，证明内存管理正确
    assert(nr_free_pages_store == nr_free_pages());
    // 打印成功信息
    cprintf("check_pgfault() succeeded!\n");
}
//page fault number
volatile unsigned int pgfault_num=0;

/* do_pgfault - interrupt handler to process the page fault execption
 * @mm         : the control struct for a set of vma using the same PDT
 * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware
 * @addr       : the addr which causes a memory access exception, (the contents of the CR2 register)
 *
 * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault
 * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing
 * the exception and recovering from it.
 *   (1) The contents of the CR2 register. The processor loads the CR2 register with the
 *       32-bit linear address that generated the exception. The do_pgfault fun can
 *       use this address to locate the corresponding page directory and page-table
 *       entries.
 *   (2) An error code on the kernel stack. The error code for a page fault has a format different from
 *       that for other exceptions. The error code tells the exception handler three things:
 *         -- The P flag   (bit 0) indicates whether the exception was due to a not-present page (0)
 *            or to either an access rights violation or the use of a reserved bit (1).
 *         -- The W/R flag (bit 1) indicates whether the memory access that caused the exception
 *            was a read (0) or write (1).
 *         -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1)
 *            or supervisor mode (0) at the time of the exception.
 */
/**
 * 处理给定内存管理上下文的页面错误。
 * 
 * @param mm 指向内存管理结构的指针。
 * @param error_code 页面错误发生时硬件提供的错误代码。
 * @param addr 引发页面错误的线性地址。
 * 
 * @return 成功返回0，失败返回负错误码。
 */
int
do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) {
    int ret = -E_INVAL;// 初始化返回值为无效错误
    //try to find a vma which include addr
    // 尝试找到包含 addr 的 vma
    struct vma_struct *vma = find_vma(mm, addr);

    pgfault_num++;// 增加页面错误计数
    // 检查 addr 是否在 mm 的 vma 范围内
    //If the addr is in the range of a mm's vma?
    if (vma == NULL || vma->vm_start > addr) {
        cprintf("not valid addr %x, and  can not find it in vma\n", addr);
        goto failed;// 跳转到错误处理部分
    }
    //check the error_code
    // 检查错误代码
    switch (error_code & 3) {
    default:
            /* 默认错误代码标志：3 (W/R=1, P=1): 写操作，存在 */
            /* error code flag : default is 3 ( W/R=1, P=1): write, present */
    case 2: /* error code flag : (W/R=1, P=0): write, not present */
            /* 错误代码标志：(W/R=1, P=0): 写操作，不存在 */
        if (!(vma->vm_flags & VM_WRITE)) {
            cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n");
            goto failed;// 跳转到错误处理部分
        }
        break;
    case 1: /* error code flag : (W/R=0, P=1): read, present */
            /* 错误代码标志：(W/R=0, P=1): 读操作，存在 */
        cprintf("do_pgfault failed: error code flag = read AND present\n");
        goto failed;// 跳转到错误处理部分
    case 0: /* error code flag : (W/R=0, P=0): read, not present */
             /* 错误代码标志：(W/R=0, P=0): 读操作，不存在 */
        if (!(vma->vm_flags & (VM_READ | VM_EXEC))) {
            cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n");
            goto failed;// 跳转到错误处理部分
        }
    }
    /* IF (write an existed addr ) OR
     *    (write an non_existed addr && addr is writable) OR
     *    (read  an non_existed addr && addr is readable)
     * THEN
     *    continue process
     */
    /* 如果 (写入已存在的地址) 或
     *    (写入不存在的地址且地址可写) 或
     *    (读取不存在的地址且地址可读)
     * 则继续处理
     */
    uint32_t perm = PTE_U;// 初始化权限标志为用户可访问
    if (vma->vm_flags & VM_WRITE) {
        perm |= PTE_W;// 如果 vma 可写，则设置写权限
    }
    addr = ROUNDDOWN(addr, PGSIZE);// 将地址对齐到页边界

    ret = -E_NO_MEM;// 初始化返回值为内存不足错误

    pte_t *ptep=NULL;
    /*LAB3 EXERCISE 1: YOUR CODE
    * Maybe you want help comment, BELOW comments can help you finish the code
    *
    * Some Useful MACROs and DEFINEs, you can use them in below implementation.
    * MACROs or Functions:
    *   get_pte : get an pte and return the kernel virtual address of this pte for la
    *             if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1')
    *   pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup
    *             an addr map pa<--->la with linear address la and the PDT pgdir
    * DEFINES:
    *   VM_WRITE  : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable
    *   PTE_W           0x002                   // page table/directory entry flags bit : Writeable
    *   PTE_U           0x004                   // page table/directory entry flags bit : User can access
    * VARIABLES:
    *   mm->pgdir : the PDT of these vma
    *
    */
   /* LAB3 练习 1: 你的代码
    * 可能需要帮助的注释，以下注释可以帮助你完成代码
    *
    * 一些有用的宏和定义，你可以在下面的实现中使用它们。
    * 宏或函数：
    *   get_pte : 获取一个页表项并返回该页表项的内核虚拟地址
    *             如果包含该页表项的页表不存在，则分配一个页表 (注意第三个参数 '1')
    *   pgdir_alloc_page : 调用 alloc_page 和 page_insert 函数分配一页大小的内存并设置
    *             地址映射 pa<--->la 与页目录表 pgdir
    * 定义：
    *   VM_WRITE  : 如果 vma->vm_flags & VM_WRITE == 1/0，则 vma 是可写/不可写的
    *   PTE_W           0x002                   // 页表/目录项标志位：可写
    *   PTE_U           0x004                   // 页表/目录项标志位：用户可访问
    * 变量：
    *   mm->pgdir : 这些 vma 的页目录表
    *
    */
#if 0
    /*LAB3 EXERCISE 1: YOUR CODE*/
    /* LAB3 练习 1: 你的代码*/
    ptep = ???              //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
                            // (1) 尝试找到一个页表项，如果该页表项的页表不存在，则创建一个页表。
    if (*ptep == 0) {
                            //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
                            // (2) 如果物理地址不存在，则分配一页内存并映射物理地址与逻辑地址
    }
    else {
    /*LAB3 EXERCISE 2: YOUR CODE
    * Now we think this pte is a  swap entry, we should load data from disk to a page with phy addr,
    * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page.
    *
    *  Some Useful MACROs and DEFINEs, you can use them in below implementation.
    *  MACROs or Functions:
    *    swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr,
    *                               find the addr of disk page, read the content of disk page into this memroy page
    *    page_insert ： build the map of phy addr of an Page with the linear addr la
    *    swap_map_swappable ： set the page swappable
    */
    /* LAB3 练习 2: 你的代码
    * 现在我们认为这个页表项是一个交换项，我们应该从磁盘加载数据到一个具有物理地址的页面，
    * 并映射物理地址与逻辑地址，触发交换管理器记录该页面的访问情况。
    *
    * 一些有用的宏和定义，你可以在下面的实现中使用它们。
    * 宏或函数：
    *   swap_in(mm, addr, &page) : 分配一个内存页面，然后根据 addr 的页表项中的交换项，
    *                              找到磁盘页面的地址，将磁盘页面的内容读入该内存页面
    *   page_insert ： 根据 mm 和 addr 设置物理地址与逻辑地址的映射
    *   swap_map_swappable ： 设置页面可交换
    */
        if(swap_init_ok) {
            struct Page *page=NULL;
                                    //(1）According to the mm AND addr, try to load the content of right disk page
                                    // (1) 根据 mm 和 addr，尝试从正确的磁盘页面加载内容到 page 管理的内存中
                                    //    into the memory which page managed.
                                    // (2) 根据 mm、addr 和 page，设置物理地址与逻辑地址的映射
                                    //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr
                                    //(3) make the page swappable.
                                    // (3) 使页面可交换
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;// 跳转到错误处理部分
        }
   }

#endif
    // try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
    // (notice the 3th parameter '1')
    // 尝试找到一个页表项 pte，如果包含该 pte 的页表不存在，则创建一个页表。
    // 注意第三个参数 '1' 表示如果需要，可以创建新的页表。
    if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) {
        cprintf("get_pte in do_pgfault failed\n");// 输出错误信息
        goto failed;// 跳转到错误处理部分
    }
    // 如果页表项 pte 的物理地址不存在，则分配一页内存并映射物理地址与逻辑地址
    if (*ptep == 0) { // if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
        if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
            cprintf("pgdir_alloc_page in do_pgfault failed\n");// 输出错误信息
            goto failed;// 跳转到错误处理部分
        }
    }
    else { // if this pte is a swap entry, then load data from disk to a page with phy addr
           // and call page_insert to map the phy addr with logical addr
           // 如果页表项 pte 是一个交换项，则从磁盘加载数据到
           //一个具有物理地址的页面，并映射物理地址与逻辑地址
        if(swap_init_ok) {// 检查交换初始化是否成功
            struct Page *page=NULL;// 声明一个页面指针
            if ((ret = swap_in(mm, addr, &page)) != 0) {
                cprintf("swap_in in do_pgfault failed\n");
                goto failed;
            }    
            page_insert(mm->pgdir, page, addr, perm);// 设置物理地址与逻辑地址的映射
            swap_map_swappable(mm, addr, page, 1);// 设置页面可交换
            page->pra_vaddr = addr;// 记录页面的虚拟地址
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;// 跳转到错误处理部分
        }
   }
   ret = 0;// 设置返回值为成功
failed:
    return ret;// 返回结果
}

